import numpy
from vectors import *
from ObjectEntity import *
import math

# for debug?
import sys

class VoxelMap:
	estimated_position = None
	estimated_intensity = None
	def __init__(self, corner1, corner2, voxelsize):
		if (not is_vec3_friendly(corner1)) or \
		   (not is_vec3_friendly(corner2)) or \
		   (not is_vec3_friendly(voxelsize)):
			raise TypeError("Expected vec3-friendly arguments for " + \
			                "corner1, corner2, voxelsize; instead, got " + \
			                "%s, %s, %s" % (str(corner1),
			                                str(corner2),
			                                str(voxelsize)))
		self.min_corner = vec3(min(corner1[0], corner2[0]), \
		                       min(corner1[1], corner2[1]), \
		                       min(corner1[2], corner2[2]))
		self.max_corner = vec3(max(corner1[0], corner2[0]), \
		                       max(corner1[1], corner2[1]), \
		                       max(corner1[2], corner2[2]))
		self.size = self.max_corner - self.min_corner
		self.voxel_size = vec3(voxelsize)
		self.resolution = vec3(int(self.size.x / self.voxel_size.x), \
		                       int(self.size.y / self.voxel_size.y), \
		                       int(self.size.z / self.voxel_size.z))
		self.n = self.resolution # for convenience in typing again
		# this exploits a flaw in vec3s that allows you to reassign elements
		# to non-floats.
		self.n.x = int(self.n.x)
		self.n.y = int(self.n.y)
		self.n.z = int(self.n.z)

		self.likelihood = None
		self.max_likelihood = None
		self.sim = None

	def msg(self, message):
		"""A message that will only be printed if debug_mode is set to True."""
		if self.sim.debug_mode: print message

	def initialize_likelihood_array(self):
		"""This is so that not all VoxelMaps have a .probability field."""
		self.likelihood = numpy.empty(tuple(self.resolution))
		self.max_likelihood = 1.0/self.likelihood.size
		self.likelihood.fill(self.max_likelihood)

	def getHotspots(self):
		"""Only works if there is a likelihood array initialized.  Returns
		a list of triples which are the voxels with the highest count.
		Also updates the max likelihood"""
		if not self.max_likelihood:
			return False
		hottest = float('-inf')
		indices = []
		for i in range(self.n.x):
			for j in range(self.n.y):
				for k in range(self.n.z):
					voxel_counts = self.likelihood[i][j][k]
					# if it's the hottest one so far
					if voxel_counts > hottest:
						hottest = voxel_counts
						indices = [(i, j, k)]
					# if it's tied for first
					elif voxel_counts == hottest:
						indices.append((i, j, k))
					# otherwise it's not a contender for hottest
					else:
						pass
		self.max_likelihood = hottest
		return indices

	def estimatePosition(self):
		"""updates the estimated_position field for the
		SweepHeatMap based on the hottest regions of space."""
		if not self.max_likelihood:
			return False
		hotspots = self.getHotspots()
		avg_pos = vec3(0,0,0)
		for i in range(len(hotspots)):
			avg_pos += self.voxelPos(hotspots[i])
		self.estimated_position = avg_pos / len(hotspots)
		return self.estimated_position
		
	def estimateIntensity(self, some_other_point=None):
		"""updates and returns the estimated_intensity field for the
		SweepHeatMap based on the current estimated_position and the data
		for the detectors.
		Consider renaming, as there is an estimateIntensity in PointEntity"""
		if some_other_point:
			if is_vec3_friendly(some_other_point):
				p = vec3(some_other_point)
			elif isinstance(PointEntity, some_other_point):
				p = some_other_point.p
			else:
				raise ValueError("If you insist on supplying some other " + \
				                 "point, is it too much to ask that such " + \
				                 "a point be usable?  What is this tripe? " + \
				                 str(some_other_point))
		else:
			if self.estimated_position:
				p = self.estimated_position
			else:
				return False
		
		estimate = 0
		num_detectors = len(self.sim.detectors)
		for i in range(num_detectors):
			d = self.sim.detectors[i]
			estimate += d.estimateIntensity(p)
		self.estimated_intensity = estimate / num_detectors
		return self.estimated_intensity

	def voxelPos(self, *args):
		"""Returns the center of the voxel with the specified indices."""
		if (len(args) == 1) and \
		   type(args[0][0]) == type(args[0][1]) == type(args[0][2]) == int:
			nx = args[0][0]
			ny = args[0][1]
			nz = args[0][2]
		elif (len(args) == 3) and \
		   type(args[0]) == type(args[1]) == type(args[2]) == int:
			nx = args[0]
			ny = args[1]
			nz = args[2]
		else:
			raise ValueError("Invalid input for voxelPos: %s" % args)
		if (nx < self.n.x) and (ny < self.n.y) and (nz < self.n.z):
			vs = self.voxel_size
			return vec3(nx * vs.x, ny * vs.y, nz * vs.z) + (vs * .5)
		else:
			raise ValueError("One or more indices out of bounds;\n" + \
			                 "This heatmap has resolution %s\n" % self.n + \
			                 "and the indices were %s" % ((nx, ny, nz),))

	def whichVoxel(self, *args):
		"""Returns the indices of the voxel in which the specified
		coordinates reside as a triple of integers. Returns -infinity for
		indices below the VoxelMap and infinity for above.
		TODO: if you'd rather have it return False, that's cool too."""
		if (len(args) == 1) and (is_vec3_friendly(args[0])):
			x = args[0][0]
			y = args[0][1]
			z = args[0][2]
		elif is_vec3_friendly(args):
			x = args[0]
			y = args[1]
			z = args[2]
		else:
			raise ValueError("Invalid input for voxelPos: %s" % args)
		min_c = self.min_corner
		max_c = self.max_corner
		vs    = self.voxel_size

		# print "%s / %s" % (x, max_c.x)
		# print "%s / %s" % (y, max_c.y)
		# print "%s / %s" % (z, max_c.z)
		# print
		if x > max_c.x:
			ix = float('inf')
		elif x == max_c.x:
			ix = self.n.x - 1
		elif x < min_c.x:
			ix = float('-inf')
		else:
			ix = (x - min_c.x) / vs.x
			
		if y > max_c.y:
			iy = float('inf')
		elif y == max_c.y:
			iy = self.n.y - 1
		elif y < min_c.y:
			iy = float('-inf')
		else:
			iy = (y - min_c.y) / vs.y
			
		if z > max_c.z:
			iz = float('inf')
		elif z == max_c.z:
			iz = self.n.z - 1
		elif z < min_c.z:
			iz = float('-inf')
		else:
			iz = (z - min_c.z) / vs.z

		return (vec3(ix, iy, iz)).int()

	def toMapCoord(self, *args):
		"""Returns the coordinate of the voxel in which the specified
		coordinates reside as a triple of integers. Returns -infinity for
		indices below the VoxelMap and infinity for above.
		"""
		if (len(args) == 1) and (is_vec3_friendly(args[0])):
			x = args[0][0]
			y = args[0][1]
			z = args[0][2]
		elif is_vec3_friendly(args):
			x = args[0]
			y = args[1]
			z = args[2]
		else:
			raise ValueError("Invalid input for voxelPos: %s" % args)
		min_c = self.min_corner
		max_c = self.max_corner
		vs    = self.voxel_size
		# Make sure that points on the maximum faces of the VoxelMap aren't
		# considered out-of-bounds.
		if x > max_c.x:
			ix = float('inf')
		elif x < min_c.x:
			ix = float('-inf')
		else:
			ix = float((x - min_c.x) / vs.x)
			
		if y > max_c.y:
			iy = float('inf')
		elif y < min_c.y:
			iy = float('-inf')
		else:
			iy = float((y - min_c.y) / vs.y)
			
		if z > max_c.z:
			iz = float('inf')
		elif z < min_c.z:
			iz = float('-inf')
		else:
			iz = float((z - min_c.z) / vs.z)

		return vec3(ix, iy, iz)


class ObstacleMap(VoxelMap):
	"""Map of Obstacles with the given resolution. Used in Voxel Traversal
	method to find all the obstacles.
	NOTE: MUST BE MAPPED AFTER ALL OBSTACLES HAVE BEEN ADDED FROM MAP FILE."""

	def __init__(self, corner1, corner2, voxelsize, obs_Dict):
		"""Initializes the mapping with the given field size and the voxelsize
		which is used to calculate the resolution of the voxel mapping.
		First and second arguments are the min and max corners of the boundary
		of the map. Third argument is the voxel size to define the resolution,
		and the last argument is the list of all obstacles."""
		if (not is_vec3_friendly(corner1)) or \
		   (not is_vec3_friendly(corner2)) or \
		   (not is_vec3_friendly(voxelsize)):
			raise TypeError("Expected vec3-friendly arguments for " + \
			                "corner1, corner2, voxelsize; instead, got " + \
			                "%s, %s, %s" % (str(corner1),
			                                str(corner2),
			                                str(voxelsize)))
		if not (type(obs_Dict) is dict):
			raise TypeError("Expected the fourth argument to be a Obstacle Dict")
		if not all(isinstance(obs_Dict[obs], Obstacle) for obs in obs_Dict):
			raise TypeError("All Elements of must be Obstacle")

		self.min_corner = vec3(min(corner1[0], corner2[0]), \
		                       min(corner1[1], corner2[1]), \
		                       min(corner1[2], corner2[2]))
		self.max_corner = vec3(max(corner1[0], corner2[0]), \
		                       max(corner1[1], corner2[1]), \
		                       max(corner1[2], corner2[2]))
		self.size = self.max_corner - self.min_corner
		self.voxel_size = vec3(voxelsize)
		self.resolution = vec3(int(self.size.x / self.voxel_size.x), \
		                       int(self.size.y / self.voxel_size.y), \
		                       int(self.size.z / self.voxel_size.z))
		self.n = self.resolution # for convenience in typing again
		# this exploits a flaw in vec3s that allows you to reassign elements
		# to non-floats.
		# this flaw is now a feature.
		self.n = self.n.int()
		
		# Mapping initialized to integer zeros for bitwise OR operation.
		self.map = numpy.zeros(tuple(self.resolution), int)
	
		# Storing the list of Obstacles
		self.obs_Dict = obs_Dict

		self.setObstacles()

	def collapseIndices(self, i):
		"""Collapses indices with inf or -inf down to usable numbers."""
		if not is_vec3_friendly(i):
			raise ValueError("Expected something vec3-friendly as an " + \
			                 "argument, got %s" % i)
		n = self.n
		temp = vec3(0,0,0)
		if   i[0] == float('inf'):
			temp.x = n.x
		elif i[0] == float('-inf'):
			temp.x = 0
		else:
			temp.x = i[0]
		if   i[0] == float('inf'):
			temp.y = n.y
		elif i[0] == float('-inf'):
			temp.y = 0
		else:
			temp.y = i[0]
		if   i[0] == float('inf'):
			temp.z = n.z
		elif i[0] == float('-inf'):
			temp.z = 0
		else:
			temp.z = i[0]
		return tempmm

	def collapseSlice(self, i1, i2):
		"""Because whichVoxel returns inf or -inf for out-of-bounds points, it
		is not always useful for slicing an array.  This translates those
		points to useful vectors of integers, returning a pair of 3-vectors
		with ints for elements."""
		n = self.n
		# these would mean the slices are entirely out of bounds 
		if ((i1.x >= n.x) and (i2.x >= n.x)) or \
		   ((i1.y >= n.y) and (i2.y >= n.y)) or \
		   ((i1.z >= n.z) and (i2.z >= n.z)) or \
		   ((i1.x < 0) and (i2.x < 0)) or \
		   ((i1.y < 0) and (i2.y < 0)) or \
		   ((i1.z < 0) and (i2.z < 0)):
			# return an empty slice
			temp = (vec3(0,0,0)).int()
			return (temp, temp)
		else:
			i_min = (vec3(max(min(i1.x, i2.x), 0), \
			              max(min(i1.y, i2.y), 0), \
			              max(min(i1.z, i2.z), 0))).int()
			i_max = (vec3(min(max(i1.x, i2.x), n.x), \
			              min(max(i1.y, i2.y), n.y), \
			              min(max(i1.z, i2.z), n.z))).int()
			return (i_min, i_max)

	def setObstacles(self):
		"""Sets all of the obstacles to the given mappings"""

		# Using long integer for mapping.
		# Since we incremented the id by one, we must use the id as the power of
		# two in order to create unique set of integer when added.
		# Note: vC stands for voxel Coordinates	
		for obs in self.obs_Dict:
			id = 2 ** self.obs_Dict[obs].id
			min_vC, max_vC = self.collapseSlice(
			                     self.whichVoxel(self.obs_Dict[obs].pt1), \
			                     self.whichVoxel(self.obs_Dict[obs].pt2))
			self.map[min_vC.x:max_vC.x+1, min_vC.y:max_vC.y+1, min_vC.z:max_vC.z +1] \
					|= id

	def removeObstacle(self, obj):	
		if not isinstance(obj, Obstacle):
			raise TypeError("Expected Obstacle as the argument, instead got " + \
							str(type(obj)))

		# Also required for unique bit per obstacle	
		# vC stands for Voxel Coordinates
		id = 2 ** obj.id
		min_vC = self.whichVoxel(obj.pt1)
		max_vC = self.whichVoxel(obj.pt2)

		# The Obstacle is not in the mapping
		if (self.map[min_vC.x][min_vC.y][min_vC.z] & id) == 0:
			raise ValueError("Object is not in the mapping")
		
		self.map[min_vC.x:max_vC.x, min_vC.y:max_vC.y, min_vC.z:max_vC.z] ^= id

	def isObstacleAtCoord(self, coord):
		"""Checks to see if the obstacle is located at the given coordinates.
		Returns True of there exists an obstacle, Returns False otherwise.
		Must input a vec3 compatible argument as point."""
		if not is_vec3_friendly(coord):
			raise TypeError("Expected something convertible to vec3" + \
							" for pts, instead got " + str(coord))

		# Similar to above, vC stande for voxel Coordinates	
		vC = self.whichVoxel(coord)
		if (float("inf") in vC) or (float("-inf") in vC):
			return False
		# print vC
		return bool(self.map[vC.x][vC.y][vC.z])

	def getObstacleAtCoord(self, coord):
		"""Returns the obstacle at the given coordinates. Returns the list of
		Obstacles if there exists an obstacle in the given coordinates.
		If there are no obstacles, it  returns an empty list.
		Must input a vec3 compatible argument as point."""
		if not is_vec3_friendly(coord):
			raise TypeError("Expected something convertible to vec3" + \
							" for pts, instead got " + str(coord))

		result = []

		if not self.isObstacleAtCoord(coord):
			return result

		# Similar to above, vC stande for voxel Coordinates	
		vC = self.whichVoxel(coord)
		totalObsId = self.map[vC.x][vC.y][vC.z]
	
		i = 1
		while i <= totalObsId:
			if (totalObsId & i):
				result.append(self.obs_Dict[int(numpy.log2(i))])
			i <<= 1

		return result

	# Required Function
	def getObstaclesBetPts(self, pt1, pt2):
		""" Returns all the obstacles between two given points"""
		if not is_vec3_friendly(pt1):
			raise TypeError("Expected something convertible to vec3" + \
							" for pts, instead got " + str(coord))


		if not is_vec3_friendly(pt2):
			raise TypeError("Expected something convertible to vec3" + \
							" for pts, instead got " + str(coord))


		# Initial parameters
		init_pt = self.toMapCoord(pt1)
		end_pt = self.toMapCoord(pt2)
		lineSrcToDet = Line(1, pt1, pt2)
		obstacles = {}

		X = init_pt.x
		Y = init_pt.y
		Z = init_pt.z

		# Must be changed later if the mapping changes
		map_max = self.n 
		map_min = vec3(0,0,0)

		# Line Equation is in the form of u + tv, where t is a scalar and 
		# u and v are vectors to each of the end points
		
		# Direction vector
		v = end_pt - init_pt
		
		# Traversing step parameters
		stepX = 1
		stepY = 1		
		stepZ = 1		
		if v.x < 0: 
			stepX = -1
		if v.y < 0:
			stepY = -1
		if v.z < 0:
			stepZ = -1
		
		# Maximum t where the line crosses the first voxel boundary
		# Note that this assumes all of the map coordinates are positive
		# If it is changed in the future, make sure to change the addition
		if v.x > 0:
			tMaxX = (int(init_pt.x + 1) - init_pt.x) / v.x
		elif v.x < 0:
			tMaxX = (int(init_pt.x) - init_pt.x) / v.x
		else:
			tMaxX = float('inf')
		
		if v.y > 0:
			tMaxY = (int(init_pt.y + 1) - init_pt.y) / v.y
		elif v.y < 0:
			tMaxY = (int(init_pt.y) - init_pt.y) / v.y
		else:
			tMaxY = float('inf')

		if v.z > 0:
			tMaxZ = (int(init_pt.z + 1) - init_pt.z) / v.z
		elif v.z < 0:
			tMaxZ = (int(init_pt.z) - init_pt.z) / v.z
		else:
			tMaxZ = float('inf')

		# Delta of t that equal one unit of voxel in the given axis
		tDeltaX = 0
		tDeltaY = 0
		tDeltaZ = 0
		# Zero Division Guard
		if v.x != 0:
			tDeltaX = 1/abs(v.x)
		if v.y != 0:
			tDeltaY = 1/abs(v.y)
		if v.z != 0:
			tDeltaZ = 1/abs(v.z)
		
		while True:
			# Debugging Purposes
			if (tMaxX < tMaxY):
				if (tMaxX < tMaxZ):
					X = X + stepX 
					if (X >= self.n.x) or (X < 0):
						break
					tMaxX += tDeltaX
				else:
					Z = Z + stepZ
					if (Z >= self.n.z) or (Z < 0):
						break
					tMaxZ += tDeltaZ
			else:
				if (tMaxY < tMaxZ):
					Y = Y + stepY
					if (Y >= self.n.y) or (Y < 0):
						break
					tMaxY += tDeltaY
				else:
					Z = Z + stepZ
					if (Z >= self.n.z) or (Z < 0):
						break
					tMaxZ += tDeltaZ

			tempObsList = self.getObstacleAtCoord((X,Y,Z))

			if tempObsList != []:
				for obs in tempObsList:
					if obs not in obstacles:
						pt = CheckLineInBox(lineSrcToDet, obs)
						if len(pt) != 0:
							dist = (pt[1] - pt[0]).mag()
							obstacles[obs] = (pt, dist)

		return obstacles
		
class KSigmaHeatMap(VoxelMap):
	"""The class which contains the probability heatmaps as used in the 
	k-sigma method"""
	simulatedSize = None
	simulatedRadLen = None
	max_x, max_y, max_z = None, None, None
	max_prob = None
	
	def Y(self, detector, point, shielding=None, assumeAir=True):
		"""Equation 7 from the MSAHS paper, adapted to 3D."""
		if not is_vec3_friendly(point):
			raise ValueError("Expected 2nd argument, point, to be" + \
			                 "vec3-friendly, but got %s" % point)
		if self.sim.time <= 0.0:
			return 1e-8
		p = vec3(point)
		dist_squared = (detector.p - p).d2()
		dist = dist_squared**.5
		if shielding:
			decay = self.decay(shielding)
		else:
			if assumeAir:
				decay = self.decay([(80, dist)])
			else:
				decay = 1
		return (detector.hits/self.sim.time - \
		        detector.size * self.sim.background) \
		       * dist_squared * decay
	
	def decay(self, shielding):
		"""Takes a list of pairs of the form [(radLen, dist) ... ] representing
		the things between the detector and the proposed point.  Intended for use
		in Y (above.)
		TODO: consider moving to a different class, or just outside
		the HeatMap class."""
		decay = 1.0
		for i in range(len(shielding)):
			radiation_length = shielding[i][0]
			distance         = shielding[i][1]
			decay *= numpy.exp(-distance / radiation_length)
		return decay
		
	def L(self, point, group_size):
		"""Likelihood estimator for a point in the field.  Intended for use
		in the update function, to update the probabilities in the heatmap."""
		if not is_vec3_friendly(point):
			raise ValueError("Expected 2nd argument, point, to be" + \
			                 "vec3-friendly, but got %s" % point)
		p = vec3(point)
		Y_array = numpy.empty(group_size)
		for i in range(len(Y_array)):
			d = self.sim.detectors[self.sim.hottest_groups[group_size][i]]
			Y_array[i] = self.Y(d, point)
		Y_mean = Y_array.mean()
		likelihood = 0.0
		for i in range(len(Y_array)):
			likelihood += (Y_array[i] - Y_mean)**2
		return 1.0 / (1 + likelihood)
	
	def update(self):
		"""Updates the likelihoods of all positions in the heatmap."""
		for i in range(self.n.x):
			for j in range(self.n.y):
				for k in range(self.n.z):
					self.likelihood[i][j][k] = self.L(self.voxelPos(i,j,k), 4)
		### TODO: abstract the number of groups


class SweepHeatMap(VoxelMap):
	"""This class contains the data arrays for experimenting with rotating
	detectors."""
	
	def makeWedges(self, num_wedges=360):
		"""creates the wedges list, which has the following format:
		wedges[detector_id][wedge_number][voxel_within_wedge] -> (i, j, k)
		num_wedges needs to be even.  This is due to the
		implementation dividing the heatmap into north and south of detector.
		NOTE: two-dimensional right now.  Operates only in the plane of
		voxels occupied by each detector.
		"""
		self.num_wedges = num_wedges
		temp = [[] for i in range(num_wedges)]
		num_detectors = len(self.sim.detectors)
		self.wedges = [temp[:] for i in range(num_detectors)]
		wedge_arc = 2 * math.pi / num_wedges
		for d_id in range(num_detectors):
			d = self.sim.detectors[d_id]
			ii_d = self.whichVoxel(d.p)
#			sys.stderr.write("Now beginning wedges for %s, which resides in %s.\n" \
#			    % (d, ii_d))#
			k = ii_d.z #bop
			
			### consider abstracting better and/or optimizing
			# north of the detector
			base = vec3(1,0,0)
#			sys.stderr.write("North partition: %s voxels.\n" \
#				% ((self.n.y - ii_d.y) * self.n.x))#
			for j in range(ii_d.y, self.n.y):
				for i in range(0, self.n.x):
					angle = base.angle(self.voxelPos(i,j,k) - d.p)
					wedge_num = int(angle / wedge_arc)
					self.wedges[d_id][wedge_num].append((i,j,k))
#					sys.stderr.write(".")#
#				sys.stderr.write("\n")#

			# south of the detector
			base = vec3(-1,0,0)
#			sys.stderr.write("South partition: %s voxels.\n" \
#				% (ii_d.y * self.n.x))
			for j in range(0, ii_d.y):
				for i in range(0, self.n.x):
					angle = base.angle(self.voxelPos(i,j,k) - d.p)
					wedge_num = int(angle / wedge_arc) + num_wedges/2
					self.wedges[d_id][wedge_num].append((i,j,k))
#					sys.stderr.write(".")#
#				sys.stderr.write("\n")#
			
			# pack it down
			sys.stderr.write("Packing down %s's individual wedge lists.\n" % d)#
			for wedge_num in range(num_wedges):
				self.wedges[d_id][wedge_num] = \
					tuple(self.wedges[d_id][wedge_num])

		# pack it down again
		sys.stderr.write("Packing down each detector's list of wedges.\n")#
		for d_id in range(num_detectors):
			self.wedges[d_id] = tuple(self.wedges[d_id])
		
		# pack the final layer
		sys.stderr.write("Packing wedges.\n")#
		self.wedges = tuple(self.wedges)
#		for d_id in range(len(self.wedges)):
#			print "Detector %d" % d_id
#			for wedge in range(len(self.wedges[d_id])):
#				print "  Wedge %d" % wedge
#				for voxel in self.wedges[d_id][wedge]:
#					print ("%12s" % str(voxel)),
#				print


	def dir2wedge(self, dir):
		"""Given a vec3 representing a direction, and the number of wedges
		in the whole circle, returns the wedge most centered on that 
		direction."""
		# error checking should go here
		
		num_wedges = self.num_wedges
		wedge_arc = 2 * math.pi / num_wedges
		# north half
		if (dir.x >= 0 and dir.y >= 0) or (dir.x < 0 and dir.y > 0):
			angle = dir.angle(vec3(1,0,0))
			return int(angle / wedge_arc)
		# south half
		else:
			angle = dir.angle(vec3(-1,0,0))
			return int(angle / wedge_arc) + num_wedges/2

	def wedge2dir(self, wedge):
		"""Given an integer representing a wedge number, and the number of
		wedges in the whole circle, returns the direction in the center of
		the wedge."""
		# The .5 places this in the middle of the wedge, rather than the
		# clockwise edge.
		num_wedges = self.num_wedges
		frac = (wedge + .5) / num_wedges
		return vec3(math.cos(2 * pi * frac), \
		            math.sin(2 * pi * frac), \
		            0)

class BumpSweepHeatMap(SweepHeatMap):
	"""the good one"""
	pass


class FlatSweepHeatMap(SweepHeatMap):
	"""This is a quick-and-incorrect method of visualizing the cones of vision."""
	def update(self, timestep=1.0):
		"""Updates the likelihoods of all positions in the heatmap."""
		for d in range(len(self.sim.detectors)):
			# we want the Detector, not its id.
			d = self.sim.detectors[d]
			# note that vec3.angle normalizes this, so we don't
			# need to here.  to_p stands for "to point".
			for e in range(len(self.sim.emitters)):
				# we want the Emitter, not its id.
				e = self.sim.emitters[e]
				# to_e is "to emitter".
				to_e = e.p - d.p
				counts = d.getRadCounts(timestep * d.arc_fraction, e)
				d.hits += counts
				for i in range(self.n.x):
					for j in range(self.n.y):
						for k in range(self.n.z):					
							to_p = self.voxelPos(i,j,k) - d.p
							if to_p.angle(to_e, d.mode) < d.theta:
								 self.likelihood[i][j][k] += counts
								



